package com.lulin.mall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lulin.common.dto.es.SkuEsModel;
import com.lulin.mall.search.config.ElasticSearchConfiguration;
import com.lulin.mall.search.constant.ESConstant;
import com.lulin.mall.search.service.MallSearchService;
import com.lulin.mall.search.vo.SearchParam;
import com.lulin.mall.search.vo.SearchResult;
import lombok.var;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author lulin
 * @date 2022/11/23 15:30
 */
@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    public RestHighLevelClient client;

    @Override
    public SearchResult search(SearchParam param) {
        SearchResult result = null;
        //1、准备检索的请求
        SearchRequest request = buildSearchRequest(param);

        try {
            //2、执行检索操作
            SearchResponse response = client.search(request, ElasticSearchConfiguration.COMMON_OPTIONS);

            //3、把检索的信息封装为SearchResult
            result = buildSearchResult(response, param);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }


    /**
     * 构建检索请求
     * 1、模糊查询、关键字匹配
     * 2、过滤（类别、品牌、属性、价格区间、库存）
     * 3、排序、分页、高亮
     * 4、聚合分析
     */
    private SearchRequest buildSearchRequest(SearchParam param) {
        //创建1个SearchRequest对象
        SearchRequest searchRequest = new SearchRequest();
        //设置要查询的索引库
        searchRequest.indices(ESConstant.PRODUCT_INDEX);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //构建具体的检索条件
        //1、构建bool查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1）关键字
        if (!StringUtils.isEmpty(param.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("subTitle", param.getKeyword()));
        }
        //2）类别
        if (param.getCatalog3Id() != null) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
        }
        //3)品牌
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
        }
        //4是否有库存
        if (param.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));
        }
        //5)价格区间
        if (!StringUtils.isEmpty(param.getSkuPrice())) {
            //我们规定的是200_300规范，根据_分割
            String[] split = param.getSkuPrice().split("_");
            //范围查询对象
            RangeQueryBuilder skuPrice = QueryBuilders.rangeQuery("skuPrice");
            if (split.length == 2) { // 说明是 200_300
                skuPrice.gte(split[0]);
                skuPrice.lte(split[1]);
            } else if (split.length == 1) {
                // 说明是 200_   _300
                if (param.getSkuPrice().endsWith("_")) {
                    //说明是 200_
                    skuPrice.gte(split[0]);
                }
                if (param.getSkuPrice().startsWith("_")) {
                    //说明是 _300
                    skuPrice.lte(split[0]);
                }
            }
            boolQuery.filter(skuPrice);
        }

        /**
         * 6）属性检索
         * 规范格式：例如：attrs=20_8英寸:10英寸&attrs=19_64GB:32GB
         * 思路：找到每个属性，每个属性做循环，每次循环，就是属性检索，
         */
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
            for (String attrStr : param.getAttrs()) {
                BoolQueryBuilder boolNestedQuery = QueryBuilders.boolQuery();
                // attrs=19_64GB:32GB——根据_来分割
                String[] attrStrArray = attrStr.split("_");
                //属性的编号
                String attrId = attrStrArray[0];
                //获取属性的值   64GB:32GB—
                String[] values = attrStrArray[1].split(":");
                //拼接组合条件——拿到值后，进行拼接
                boolNestedQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                boolNestedQuery.must(QueryBuilders.termsQuery("attrs.attrValue", values));

                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", boolNestedQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }
        sourceBuilder.query(boolQuery);

        //2、排序
        if (!StringUtils.isEmpty(param.getSort())) {
            //规范：sort=salaCount_asc/desc  根据_来分割 前是培训字段，后面是排序的方式（升序/降序）
            String[] sortArray = param.getSort().split("_");
            SortOrder sortOrder = sortArray[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
            sourceBuilder.sort(sortArray[0], sortOrder);
        }

        /**
         * 3、处理分页
         * 只传了 pageNum
         */
        if (param.getPageNum() != null) {
            /**
             * 分页处理
             *  pageNum：页码，就是第几页，默认是第1页
             *  pageSize = 5 ：每页显示5条
             *      pageNum:1 from:0  [0,1,2,3,4]
             *      pageNum:2 from:5 [5,6,7,8,9]
             *      pageNum:3 from:10 [11,12,13,14,15]
             *     from = ( pageNum - 1 ) * pageSize
             */
            sourceBuilder.from((param.getPageNum() - 1) * ESConstant.PRODUCT_PAGESIZE);
            sourceBuilder.size(ESConstant.PRODUCT_PAGESIZE);//每页显示5条
        }
        //4、高亮
        if (!StringUtils.isEmpty(param.getKeyword())) {
            //根据关键字查询，才需要设置高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("subTitle");
            highlightBuilder.preTags("<b style='color:red' >");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }
        //5、聚合查询
        //1）品牌的聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId");
        brand_agg.size(50);//显示50个
        //品牌的字聚合——品牌的名称和logo图片
        brand_agg.subAggregation(AggregationBuilders.terms("brandName_agg").field("brandName").size(10));
        brand_agg.subAggregation(AggregationBuilders.terms("brandImg_agg").field("brandImg").size(10));

        sourceBuilder.aggregation(brand_agg);

        //2）类目的聚合
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(10);
        catalog_agg.subAggregation(AggregationBuilders.terms("catalogName_agg").field("catalogName").size(10));
        sourceBuilder.aggregation(catalog_agg);

        //3）属性的聚合
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
        //属性id聚合
        TermsAggregationBuilder attrId_agg = AggregationBuilders.terms("attrId_agg").field("attrs.attrId").size(10);
        //属性id下的子类聚合——属性名称和属性值
        attrId_agg.subAggregation(AggregationBuilders.terms("attrName_agg").field("attrs.attrName").size(10));
        attrId_agg.subAggregation(AggregationBuilders.terms("attrValue_agg").field("attrs.attrValue").size(10));
        attr_agg.subAggregation(attrId_agg);

        sourceBuilder.aggregation(attr_agg);

        //打印测试
        System.out.println("DSL语句：" + sourceBuilder.toString());
        searchRequest.source(sourceBuilder);
        return searchRequest;
    }

    /**
     * 根据检索的结果解析，封装为SearchResult对象
     */
    private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {
        SearchResult result = new SearchResult();
        SearchHits hits = response.getHits();
        //检索的所有商品信息
        SearchHit[] products = hits.getHits();
        List<SkuEsModel> esModels = new ArrayList<>();
        if (products != null && products.length > 0) {
            for (SearchHit product : products) {
                String sourceAsString = product.getSourceAsString();
                //把json转为SkuEsModel对象
                SkuEsModel model = JSON.parseObject(sourceAsString, SkuEsModel.class);
                if (!StringUtils.isEmpty(param.getKeyword())) {
                    //根据subTitle字段，设置高亮
                    HighlightField subTitle = product.getHighlightFields().get("subTitle");
                    String subTitleHlight = subTitle.getFragments()[0].string();
                    //设置高亮
                    model.setSubTitle(subTitleHlight);
                }
                esModels.add(model);
            }
        }
        result.setProducts(esModels);

        //2、当前商品所涉及到的索引品牌
        Aggregations aggregations = response.getAggregations();
        //根据聚合的品牌的名称，找到这个桶
        ParsedLongTerms brand_agg = aggregations.get("brand_agg");
        //存储所有品牌的容器
        List<SearchResult.BrandVO> brandVOS = new ArrayList<>();
        List<? extends Terms.Bucket> buckets = brand_agg.getBuckets();
        if (buckets != null && buckets.size() > 0) {
            for (Terms.Bucket bucket : buckets) {
                SearchResult.BrandVO brandVO = new SearchResult.BrandVO();
                //获取品牌的key
                String keyAsString = bucket.getKeyAsString();
                //设置品牌编号
                brandVO.setBrandId(Long.parseLong(keyAsString));
                //然后，获取品牌名称和图片的地址
                ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brandImg_agg");
                List<? extends Terms.Bucket> brandImgAggBuckets = brandImgAgg.getBuckets();
                if (brandImgAggBuckets != null && brandImgAggBuckets.size() > 0) {
                    //获取全部品牌图片的地址，并设置
                    String img = brandImgAggBuckets.get(0).getKeyAsString();
                    brandVO.setBrandImg(img);
                }
                //获取品牌名称
                ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandName_agg");
                String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                brandVO.setBrandName(brandName);
                //添加到list容器中
                brandVOS.add(brandVO);
            }
        }
        result.setBrands(brandVOS);

        //3、当前商品涉及到的类别信息
        ParsedLongTerms catalogAgg = aggregations.get("catalog_agg");
        List<? extends Terms.Bucket> catalogAggBuckets = catalogAgg.getBuckets();
        //存储所有类别的容器
        List<SearchResult.CatalogVO> catalogVOS = new ArrayList<>();

        if (catalogAggBuckets != null && catalogAggBuckets.size() > 0) {
            for (Terms.Bucket bucket : catalogAggBuckets) {
                SearchResult.CatalogVO catalogVO = new SearchResult.CatalogVO();
                //获取类别编号
                String catalogId = bucket.getKeyAsString();
                //设置类别编号
                catalogVO.setCatalogId(Long.parseLong(catalogId));

                //获取类别名称
                ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalogName_agg");
                String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
                catalogVO.setCatalogName(catalogName);
                catalogVOS.add(catalogVO);
            }
        }
        result.setCatalogs(catalogVOS);

        //4、当前商品涉及到的属性信息
        ParsedNested attr_agg = aggregations.get("attr_agg");
        ParsedLongTerms attrId_agg = attr_agg.getAggregations().get("attrId_agg");
        List<? extends Terms.Bucket> attrAggBuckets = attrId_agg.getBuckets();
        //存储所有属性的容器
        List<SearchResult.AttrVo> attrVos = new ArrayList<>();

        if (attrAggBuckets != null && attrAggBuckets.size() > 0) {
            for (Terms.Bucket bucket : attrAggBuckets) {
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                //设置属性编号
                String attrId = bucket.getKeyAsString();
                attrVo.setAttrId(Long.parseLong(attrId));
                //属性名称
                ParsedStringTerms attrName_agg = bucket.getAggregations().get("attrName_agg");
                String attrName = attrName_agg.getBuckets().get(0).getKeyAsString();
                attrVo.setAttrName(attrName);
                //属性值
                ParsedStringTerms attrValue_agg = bucket.getAggregations().get("attrValue_agg");
                if (attrValue_agg.getBuckets() != null && attrValue_agg.getBuckets().size() > 0) {
                    String attrValue = attrValue_agg.getBuckets().get(0).getKeyAsString();
                    //查询多个属性的值，拼接为1个集合
                    List<String> values = attrValue_agg.getBuckets().stream().map(item -> {
                        return item.getKeyAsString();
                    }).collect(Collectors.toList());
                    //设置属性值
                    attrVo.setAttrValue(values);
                }
                attrVos.add(attrVo);
            }
        }
        result.setAttrs(attrVos);

        //5、分页信息 当前页 总记录数  总页数
        long totalCount = hits.getTotalHits().value;
        result.setTotal(totalCount);//总记录数
        result.setPageNum(param.getPageNum());//设置当前页
        //总页数=总记录数/每页条数 一共6条，每页是5条  6/5=1 页，但是超过1条，就+1
        long totalPage = totalCount % ESConstant.PRODUCT_PAGESIZE == 0 ? totalCount / ESConstant.PRODUCT_PAGESIZE : (totalCount / ESConstant.PRODUCT_PAGESIZE + 1);
        //分页的信息——页面的第几页
        List<Integer> navs=new ArrayList<>();
        for (int i = 1; i <= totalPage; i++) {
            navs.add(i);
        }
        result.setNavs(navs);
        result.setTotalPages((int) totalPage);

        return result;
    }
}
